#pragma once

#include "znx_defs.h"
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/eventfd.h>

/*
 * wrapping syscall api,
 * standardizing their return values and enhancing their security.
 */


typedef struct timespec         znx_timespec_t;
typedef struct sockaddr         znx_sockaddr_t;
typedef struct epoll_event      znx_epoll_event_t;
typedef struct iovec            znx_iovec_t;
typedef struct msghdr           znx_msghdr_t;
typedef struct mmsghdr          znx_mmsghdr_t;
typedef struct stat             znx_file_stat_t;


#define SYSCALL_RETURN_1(ret)   \
    return (ret == -1) ? (-errno) : (ret)
#define SYSCALL_RETURN_2(ret)   \
    return (ret == 0) ? 0 : (-ret)


static inline znx_result_t
znx_syscall_epoll_create(int size)
{
    int ret = epoll_create(size);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_epoll_ctl(int epfd, int op, int fd, znx_epoll_event_t *event)
{
    int ret = epoll_ctl(epfd, op, fd, event);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_epoll_wait(int epfd, znx_epoll_event_t *events, int maxevents,
    int timeout)
{
    int ret = epoll_wait(epfd, events, maxevents, timeout);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_eventfd(unsigned int initval, int flags)
{
    int ret = eventfd(initval, flags);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_socket(int domain, int type, int protocol)
{
    int ret = socket(domain, type, protocol);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_listen(int sockfd, int backlog)
{
    int ret = listen(sockfd, backlog);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_bind(int sockfd, const znx_sockaddr_t *addr, socklen_t addrlen)
{
    int ret = bind(sockfd, addr, addrlen);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_accept(int sockfd, znx_sockaddr_t *addr, socklen_t *addrlen)
{
    int ret = accept(sockfd, addr, addrlen);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_accept4(int sockfd, znx_sockaddr_t *addr, socklen_t *addrlen, int flags)
{
    int ret = accept4(sockfd, addr, addrlen, flags);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_connect(int sockfd, const znx_sockaddr_t *addr, socklen_t addrlen)
{
    int ret = connect(sockfd, addr, addrlen);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_open_nomode(const char *pathname, int flags)
{
    int ret = open(pathname, flags);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_open(const char *pathname, int flags, mode_t mode)
{
    int ret = open(pathname, flags, mode);
    SYSCALL_RETURN_1(ret);
}

static inline void
znx_syscall_close(int fd)
{
    if (close(fd) == -1) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline void
znx_syscall_shutdown(int sockfd, int how)
{
    if (shutdown(sockfd, how) == -1) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline znx_result_t
znx_syscall_ioctl(int fd, unsigned long request, void *argp)
{
    int ret = ioctl(fd, request, argp);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen)
{
    int ret = getsockopt(sockfd, level, optname, optval, optlen);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
{
    int ret = setsockopt(sockfd, level, optname, optval, optlen);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_fcntl(int fd, int cmd, int arg)
{
    int ret = -1;
    do {
        ret = fcntl(fd, cmd, arg);
    } while (ret < 0 && (errno == EAGAIN || errno == EINTR));

    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_set_fcntl(int fd, int arg)
{
    znx_result_t ret = znx_syscall_fcntl(fd, F_GETFL, 0);
    if (ret < 0) {
        return ret;
    }

    int flags = (int)ret;
    flags |= arg;
    return znx_syscall_fcntl(fd, F_SETFL, flags);
}

static inline znx_result_t
znx_syscall_clear_fcntl(int fd, int arg)
{
    znx_result_t ret = znx_syscall_fcntl(fd, F_GETFL, 0);
    if (ret < 0) {
        return ret;
    }

    int flags = (int)ret;
    flags &= -arg;
    return znx_syscall_fcntl(fd, F_SETFL, flags);
}

static inline znx_result_t
znx_syscall_set_nonblock(int fd)
{
    return znx_syscall_set_fcntl(fd, O_NONBLOCK);
}

static inline znx_result_t
znx_syscall_set_blocking(int fd)
{
    return znx_syscall_clear_fcntl(fd, O_NONBLOCK);
}

static inline znx_result_t
znx_syscall_set_close_exec(int fd)
{
    return znx_syscall_set_fcntl(fd, FD_CLOEXEC);
}

static inline znx_result_t
znx_syscall_clear_close_exec(int fd)
{
    return znx_syscall_clear_fcntl(fd, FD_CLOEXEC);
}

static inline znx_result_t
znx_syscall_read(int fd, void *buf, size_t count)
{
    int64_t r = -1;
    do {
        r = read(fd, buf, count);
    } while(r < 0 && errno == EINTR);

    SYSCALL_RETURN_1(r);
}

static inline znx_result_t
znx_syscall_readv(int fd, const znx_iovec_t *vector, int count)
{
    int64_t r = -1;
    do {
        r = readv(fd, vector, count);
    } while(r < 0 && errno == EINTR);

    SYSCALL_RETURN_1(r);
}

static inline znx_result_t
znx_syscall_write(int fd, void *buf, size_t count)
{
    int64_t r = -1;
    do {
        r = write(fd, buf, count);
    } while(r < 0 && errno == EINTR);

    SYSCALL_RETURN_1(r);
}

static inline znx_result_t
znx_syscall_writev(int fd, const znx_iovec_t *vector, int count)
{
    int64_t r = -1;
    do {
        r = writev(fd, vector, count);
    } while(r < 0 && errno == EINTR);

    SYSCALL_RETURN_1(r);
}

static inline znx_result_t
znx_syscall_recvmsg(int sockfd, znx_msghdr_t *msg, int flags)
{
    int64_t r = -1;
    do {
        r = recvmsg(sockfd, msg, flags);
    } while(r < 0 && errno == EINTR);

    SYSCALL_RETURN_1(r);
}

static inline znx_result_t
znx_syscall_recvmmsg(int sockfd, znx_mmsghdr_t *msgvec, unsigned int vlen,
    int flags, znx_timespec_t *timeout)
{
    int ret = recvmmsg(sockfd, msgvec, vlen, flags, timeout);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_sendmsg(int sockfd, const znx_msghdr_t *msg, int flags)
{
    int64_t r = -1;
    do {
        r = sendmsg(sockfd, msg, flags);
    } while(r < 0 && errno == EINTR);

    SYSCALL_RETURN_1(r);
}

static inline znx_result_t
znx_syscall_sendmmsg(int sockfd, znx_mmsghdr_t *msgvec, unsigned int vlen,
    int flags)
{
    int ret = sendmmsg(sockfd, msgvec, vlen, flags);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_send(int sockfd, const char *buf, size_t len, int flags)
{
    ssize_t ret = send(sockfd, buf, len, flags);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_recv(int sockfd, void *buf, size_t len, int flags)
{
    ssize_t ret = recv(sockfd, buf, len, flags);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_pread(int fd, void *buf, size_t nbytes, off_t offset)
{
    int64_t ret = pread(fd, buf, nbytes, offset);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_pwrite(int fd, void *buf, size_t nbytes, off_t offset)
{
    int64_t ret = pwrite(fd, buf, nbytes, offset);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_fallocate(int fd, int64_t offset, int64_t len)
{
    int ret = posix_fallocate(fd, offset, len);
    SYSCALL_RETURN_2(ret);
}

static inline znx_result_t
znx_syscall_file_stat(const char *file_path, znx_file_stat_t *stat_buf)
{
    int ret = stat(file_path, stat_buf);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_file_fstat(int fd, znx_file_stat_t *stat_buf)
{
    int ret = fstat(fd, stat_buf);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_ftruncate(int fd, int64_t length)
{
    int ret = ftruncate(fd, length);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_shm_open(const char *name, int oflag, mode_t mode)
{
    int ret = shm_open(name, oflag, mode);
    SYSCALL_RETURN_1(ret);
}

static inline void
znx_syscall_shm_unlink(const char *name)
{
    shm_unlink(name);
}

static inline znx_result_t
znx_syscall_mmap(void *addr, int64_t length, int prot, int flags, int fd,
    int64_t offset)
{

    void *raddr = mmap(addr, (size_t)length, prot, flags, fd, offset);
    if (raddr == MAP_FAILED) {
        return ZNX_FAILED;
    }

    return (int64_t)(intptr_t)raddr;
}

static inline znx_result_t
znx_syscall_munmap(void *addr, int64_t length)
{
    int ret = munmap(addr, (size_t)length);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_getsockname(int sockfd, znx_sockaddr_t *addr, socklen_t *addrlen)
{
    int ret = getsockname(sockfd, addr, addrlen);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_getpeername(int sockfd, znx_sockaddr_t *addr, socklen_t *addrlen)
{
    int ret = getpeername(sockfd, addr, addrlen);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_link(const char *oldpath, const char *newpath)
{
    int ret = link(oldpath, newpath);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_symlink(const char *target, const char *linkpath)
{
    int ret = symlink(target, linkpath);
    SYSCALL_RETURN_1(ret);
}

static inline znx_result_t
znx_syscall_unlink(const char *pathname)
{
    int ret = unlink(pathname);
    SYSCALL_RETURN_1(ret);
}

///////////////////// threads ///////////////////

typedef pthread_t           znx_thread_t;
typedef void                *(znx_thread_routine_pt)(void *);
typedef pthread_attr_t      znx_thread_attr_t;

static inline znx_thread_t
znx_thread_create(znx_thread_routine_pt start_routine, void *args,
    znx_bool_t detached)
{
    int                 ret;
    znx_thread_t        thread_id;
    znx_thread_attr_t   attr;

    pthread_attr_init(&attr);
	if(detached) {
		pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	}

    ret = pthread_create(&thread_id, NULL, start_routine, args);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }

    pthread_attr_destroy(&attr);

    return thread_id;
}

/* if join failed, we will abort process.
 * @params ret: the user only needs to pass a null ptr
 * if the ret is not null after the function return, caller should free it after using it.
 */
static inline void
znx_thread_join(znx_thread_t thread_id, void **ret)
{
    int res = pthread_join(thread_id, ret);
    if (res != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline znx_thread_t
znx_thread_self()
{
    return pthread_self();
}

static inline void
znx_thread_exit(void *status)
{
    pthread_exit(status);
}

static inline znx_result_t
znx_thread_cancel(znx_thread_t who)
{
    int ret = pthread_cancel(who);
    SYSCALL_RETURN_1(ret);
}


typedef pthread_mutex_t         znx_mutex_t;
#define ZNX_MUTEX_INITIALIZER   PTHREAD_MUTEX_INITIALIZER

// if pthread_mutex_init failed, we will abort process.
static inline void
znx_mutex_init(znx_mutex_t *m)
{
    int ret = pthread_mutex_init(m, NULL);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

// if pthread_mutex_destroy failed, we will abort process.
static inline void
znx_mutex_destroy(znx_mutex_t *m)
{
    int ret = pthread_mutex_destroy(m);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

// if pthread_mutex_lock failed, we will abort process.
static inline void
znx_mutex_lock(znx_mutex_t *m)
{
    int ret = pthread_mutex_lock(m);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline znx_bool_t
znx_mutex_trylock(znx_mutex_t *m)
{
    int ret = pthread_mutex_trylock(m);
    if (ret == 0) {
        return ZNX_TRUE;
    }
    return ZNX_FALSE;
}

static inline void
znx_mutex_unlock(znx_mutex_t *m)
{
    int ret = pthread_mutex_unlock(m);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}


typedef pthread_cond_t          znx_cond_t;
#define ZNX_COND_INITIALIZER    PTHREAD_COND_INITIALIZER

static inline void
znx_cond_init(znx_cond_t *c)
{
    int ret = pthread_cond_init(c, NULL);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline void
znx_cond_destroy(znx_cond_t *c)
{
    int ret = pthread_cond_destroy(c);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline void
znx_cond_wait(znx_cond_t *c, znx_mutex_t *m)
{
    int ret;

    while(EINTR == (ret = pthread_cond_wait(c, m))) {}

    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline void
znx_cond_time_wait(znx_cond_t *c, znx_mutex_t *m, znx_timespec_t *ts)
{
    int ret;

    while(EINTR == (ret = pthread_cond_timedwait(c, m, ts))) {}

    if (ret != 0 && ret != ETIMEDOUT) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline void
znx_cond_signal(znx_cond_t *c)
{
    int ret = pthread_cond_signal(c);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline void
znx_cond_broadcast(znx_cond_t *c) {
    int ret = pthread_cond_broadcast(c);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}


typedef pthread_key_t           znx_thread_key_t;

static inline void
znx_thread_key_create(znx_thread_key_t *key, void (*destr_func)(void *value))
{
    int ret = pthread_key_create(key, destr_func);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline void
znx_thread_key_delete(znx_thread_key_t key)
{
    int ret = pthread_key_delete(key);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline void
znx_thread_setspecific(znx_thread_key_t key, void *value)
{
    int ret = pthread_setspecific(key, value);
    if (ret != 0) {
        ZNX_ASSERT_FATAL("unexpected result");
    }
}

static inline void *
znx_thread_getspecific(znx_thread_key_t key)
{
    return pthread_getspecific(key);
}

static inline void
znx_thread_set_name(znx_thread_t tid, const char *name) {
    pthread_setname_np(tid, name);
}
